/* * Copyright 2009, Google Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * @fileoverview This file contains objects to deal with basic webgl stuff. */ tdl.provide('tdl.webgl'); tdl.require('tdl.log'); tdl.require('tdl.misc'); /** * A module for log. * @namespace */ tdl.webgl = tdl.webgl || {}; /** * The current GL context * @type {WebGLRenderingContext} */ gl = null; tdl.webgl.makeCurrent = function(context) { gl = context; } /** * Creates the HTLM for a failure message * @param {string} canvasContainerId id of container of th * canvas. * @return {string} The html. */ tdl.webgl.makeFailHTML = function(msg) { return '' + '' + '
' + '
' + '
' + msg + '
' + '
' + '
'; }; /** * Mesasge for getting a webgl browser * @type {string} */ tdl.webgl.GET_A_WEBGL_BROWSER = '' + 'This page requires a browser that supports WebGL.
' + 'Click here to upgrade your browser.'; /** * Mesasge for need better hardware * @type {string} */ tdl.webgl.OTHER_PROBLEM = '' + "It does not appear your computer supports WebGL.
" + 'Click here for more information.'; /** * Creates a webgl context. * @param {Element} canvas. The canvas element to create a * context from. * @param {WebGLContextCreationAttirbutes} opt_attribs Any * creation attributes you want to pass in. * @param {function:(msg)} opt_onError An function to call * if there is an error during creation. * @return {!WebGLRenderingContext} The created context. */ tdl.webgl.setupWebGL = function(canvas, opt_attribs, opt_onError, opt_preferredContextType) { function handleCreationError(msg) { var container = canvas.parentNode; if (container) { var str = window.WebGLRenderingContext ? tdl.webgl.OTHER_PROBLEM : tdl.webgl.GET_A_WEBGL_BROWSER; if (msg) { str += "

Status: " + msg; } container.innerHTML = tdl.webgl.makeFailHTML(str); } }; opt_onError = opt_onError || handleCreationError; if (canvas.addEventListener) { canvas.addEventListener("webglcontextcreationerror", function(event) { opt_onError(event.statusMessage); }, false); } var context = tdl.webgl.create3DContext(canvas, opt_attribs, opt_preferredContextType); if (context) { if (canvas.addEventListener) { canvas.addEventListener("webglcontextlost", function(event) { //tdl.log("call tdl.webgl.handleContextLost"); event.preventDefault(); tdl.webgl.handleContextLost(canvas); }, false); canvas.addEventListener("webglcontextrestored", function(event) { //tdl.log("call tdl.webgl.handleContextRestored"); tdl.webgl.handleContextRestored(canvas); }, false); } } else { if (!window.WebGLRenderingContext) { opt_onError(""); } } return context; }; /** * Creates a webgl context. * @param {!Canvas} canvas The canvas tag to get context * from. If one is not passed in one will be created. * @return {!WebGLRenderingContext} The created context. */ tdl.webgl.create3DContext = function(canvas, opt_attribs, opt_preferredContextType) { if (opt_attribs === undefined) { opt_attribs = {alpha:false}; tdl.misc.applyUrlSettings(opt_attribs, 'webgl'); } var names = ["webgl", "experimental-webgl"]; if (opt_preferredContextType) { names.splice(0, 0, opt_preferredContextType); } var context = null; for (var ii = 0; ii < names.length; ++ii) { try { context = canvas.getContext(names[ii], opt_attribs); } catch(e) {} if (context) { break; } } if (context) { if (!tdl.webgl.glEnums) { tdl.webgl.init(context); } tdl.webgl.makeCurrent(context); tdl.webgl.setupCanvas_(canvas); context.tdl = {}; context.tdl.depthTexture = tdl.webgl.getExtensionWithKnownPrefixes("WEBGL_depth_texture"); // Disallow selection by default. This keeps the cursor from changing to an // I-beam when the user clicks and drags. It's easier on the eyes. function returnFalse() { return false; } canvas.onselectstart = returnFalse; canvas.onmousedown = returnFalse; } return context; }; tdl.webgl.setupCanvas_ = function(canvas) { if (!canvas.tdl) { canvas.tdl = {}; } }; /** * Browser prefixes for extensions. * @type {!Array.} */ tdl.webgl.browserPrefixes_ = [ "", "MOZ_", "OP_", "WEBKIT_" ]; /** * Given an extension name like WEBGL_compressed_texture_s3tc * returns the supported version extension, like * WEBKIT_WEBGL_compressed_teture_s3tc * @param {string} name Name of extension to look for * @return {WebGLExtension} The extension or undefined if not * found. */ tdl.webgl.getExtensionWithKnownPrefixes = function(name) { for (var ii = 0; ii < tdl.webgl.browserPrefixes_.length; ++ii) { var prefixedName = tdl.webgl.browserPrefixes_[ii] + name; var ext = gl.getExtension(prefixedName); if (ext) { return ext; } } }; tdl.webgl.runHandlers_ = function(handlers) { //tdl.log("run handlers: " + handlers.length); var handlersCopy = handlers.slice(); for (var ii = 0; ii < handlersCopy.length; ++ii) { //tdl.log("run: " + ii); handlersCopy[ii](); } }; tdl.webgl.registerContextLostHandler = function( canvas, handler, opt_sysHandler) { tdl.webgl.setupCanvas_(canvas); if (!canvas.tdl.contextLostHandlers) { canvas.tdl.contextLostHandlers = [[],[]]; } var a = canvas.tdl.contextLostHandlers[opt_sysHandler ? 0 : 1]; a.push(handler); }; tdl.webgl.registerContextRestoredHandler = function( canvas, handler, opt_sysHandler) { tdl.webgl.setupCanvas_(canvas); if (!canvas.tdl.contextRestoredHandlers) { canvas.tdl.contextRestoredHandlers = [[],[]]; } var a = canvas.tdl.contextRestoredHandlers[opt_sysHandler ? 0 : 1]; a.push(handler); }; tdl.webgl.handleContextLost = function(canvas) { // first run tdl's handlers then the user's //tdl.log("tdl.webgl.handleContextLost"); if (canvas.tdl.contextLostHandlers) { tdl.webgl.runHandlers_(canvas.tdl.contextLostHandlers[0]); tdl.webgl.runHandlers_(canvas.tdl.contextLostHandlers[1]); } }; tdl.webgl.handleContextRestored = function(canvas) { // first run tdl's handlers then the user's //tdl.log("tdl.webgl.handleContextRestored"); if (canvas.tdl.contextRestoredHandlers) { tdl.webgl.runHandlers_(canvas.tdl.contextRestoredHandlers[0]); tdl.webgl.runHandlers_(canvas.tdl.contextRestoredHandlers[1]); } }; /** * Which arguements are enums. * @type {!Object.} */ tdl.webgl.glValidEnumContexts = { // Generic setters and getters 'enable': { 0:true }, 'disable': { 0:true }, 'getParameter': { 0:true }, // Rendering 'drawArrays': { 0:true }, 'drawElements': { 0:true, 2:true }, // Shaders 'createShader': { 0:true }, 'getShaderParameter': { 1:true }, 'getProgramParameter': { 1:true }, // Vertex attributes 'getVertexAttrib': { 1:true }, 'vertexAttribPointer': { 2:true }, // Textures 'bindTexture': { 0:true }, 'activeTexture': { 0:true }, 'getTexParameter': { 0:true, 1:true }, 'texParameterf': { 0:true, 1:true }, 'texParameteri': { 0:true, 1:true, 2:true }, 'texImage2D': { 0:true, 2:true, 6:true, 7:true }, 'texSubImage2D': { 0:true, 6:true, 7:true }, 'copyTexImage2D': { 0:true, 2:true }, 'copyTexSubImage2D': { 0:true }, 'generateMipmap': { 0:true }, // Buffer objects 'bindBuffer': { 0:true }, 'bufferData': { 0:true, 2:true }, 'bufferSubData': { 0:true }, 'getBufferParameter': { 0:true, 1:true }, // Renderbuffers and framebuffers 'pixelStorei': { 0:true, 1:true }, 'readPixels': { 4:true, 5:true }, 'bindRenderbuffer': { 0:true }, 'bindFramebuffer': { 0:true }, 'checkFramebufferStatus': { 0:true }, 'framebufferRenderbuffer': { 0:true, 1:true, 2:true }, 'framebufferTexture2D': { 0:true, 1:true, 2:true }, 'getFramebufferAttachmentParameter': { 0:true, 1:true, 2:true }, 'getRenderbufferParameter': { 0:true, 1:true }, 'renderbufferStorage': { 0:true, 1:true }, // Frame buffer operations (clear, blend, depth test, stencil) 'clear': { 0:true }, 'depthFunc': { 0:true }, 'blendFunc': { 0:true, 1:true }, 'blendFuncSeparate': { 0:true, 1:true, 2:true, 3:true }, 'blendEquation': { 0:true }, 'blendEquationSeparate': { 0:true, 1:true }, 'stencilFunc': { 0:true }, 'stencilFuncSeparate': { 0:true, 1:true }, 'stencilMaskSeparate': { 0:true }, 'stencilOp': { 0:true, 1:true, 2:true }, 'stencilOpSeparate': { 0:true, 1:true, 2:true, 3:true }, // Culling 'cullFace': { 0:true }, 'frontFace': { 0:true } }; /** * Map of numbers to names. * @type {Object} */ tdl.webgl.glEnums = null; /** * Initializes this module. Safe to call more than once. * @param {!WebGLRenderingContext} ctx A WebGL context. If * you have more than one context it doesn't matter which one * you pass in, it is only used to pull out constants. */ tdl.webgl.init = function(ctx) { if (tdl.webgl.glEnums == null) { tdl.webgl.glEnums = { }; for (var propertyName in ctx) { if (typeof ctx[propertyName] == 'number') { tdl.webgl.glEnums[ctx[propertyName]] = propertyName; } } } }; /** * Checks the utils have been initialized. */ tdl.webgl.checkInit = function() { if (tdl.webgl.glEnums == null) { throw 'tdl.webgl.init(ctx) not called'; } }; /** * Returns true or false if value matches any WebGL enum * @param {*} value Value to check if it might be an enum. * @return {boolean} True if value matches one of the WebGL defined enums */ tdl.webgl.mightBeEnum = function(value) { tdl.webgl.checkInit(); return (tdl.webgl.glEnums[value] !== undefined); } /** * Gets an string version of an WebGL enum. * * Example: * var str = WebGLDebugUtil.glEnumToString(ctx.getError()); * * @param {number} value Value to return an enum for * @return {string} The string version of the enum. */ tdl.webgl.glEnumToString = function(value) { tdl.webgl.checkInit(); if (value === undefined) { return "undefined"; } var name = tdl.webgl.glEnums[value]; return (name !== undefined) ? name : ("*UNKNOWN WebGL ENUM (0x" + value.toString(16) + ")"); }; /** * Returns the string version of a WebGL argument. * Attempts to convert enum arguments to strings. * @param {string} functionName the name of the WebGL function. * @param {number} argumentIndx the index of the argument. * @param {*} value The value of the argument. * @return {string} The value as a string. */ tdl.webgl.glFunctionArgToString = function(functionName, argumentIndex, value) { var funcInfo = tdl.webgl.glValidEnumContexts[functionName]; if (funcInfo !== undefined) { if (funcInfo[argumentIndex]) { return tdl.webgl.glEnumToString(value); } } if (value === null) { return "null"; } else if (value === undefined) { return "undefined"; } else { return value.toString(); } }; /** * Converts the arguments of a WebGL function to a string. * Attempts to convert enum arguments to strings. * * @param {string} functionName the name of the WebGL function. * @param {number} args The arguments. * @return {string} The arguments as a string. */ tdl.webgl.glFunctionArgsToString = function(functionName, args) { // apparently we can't do args.join(","); var argStr = ""; for (var ii = 0; ii < args.length; ++ii) { argStr += ((ii == 0) ? '' : ', ') + tdl.webgl.glFunctionArgToString(functionName, ii, args[ii]); } return argStr; }; /** * Given a WebGL context returns a wrapped context that calls * gl.getError after every command and calls a function if the * result is not gl.NO_ERROR. * * @param {!WebGLRenderingContext} ctx The webgl context to * wrap. * @param {!function(err, funcName, args): void} opt_onErrorFunc * The function to call when gl.getError returns an * error. If not specified the default function calls * console.log with a message. * @param {!function(funcName, args): void} opt_onFunc The * function to call when each webgl function is called. * You can use this to log all calls for example. */ tdl.webgl.makeDebugContext = function(ctx, opt_onErrorFunc, opt_onFunc) { tdl.webgl.init(ctx); opt_onErrorFunc = opt_onErrorFunc || function(err, functionName, args) { tdl.error( "WebGL error "+ tdl.webgl.glEnumToString(err) + " in " + functionName + "(" + tdl.webgl.glFunctionArgsToString( functionName, args) + ")"); }; // Holds booleans for each GL error so after we get the error ourselves // we can still return it to the client app. var glErrorShadow = { }; // Makes a function that calls a WebGL function and then calls getError. function makeErrorWrapper(ctx, functionName) { return function() { if (opt_onFunc) { opt_onFunc(functionName, arguments); } try { var result = ctx[functionName].apply(ctx, arguments); } catch (e) { opt_onErrorFunc(ctx.NO_ERROR, functionName, arguments); throw(e); } var err = ctx.getError(); if (err != 0) { glErrorShadow[err] = true; opt_onErrorFunc(err, functionName, arguments); } return result; }; } function makePropertyWrapper(wrapper, original, propertyName) { wrapper.__defineGetter__(propertyName, function() { return original[propertyName]; }); // TODO(gmane): this needs to handle properties that take more than // one value? wrapper.__defineSetter__(propertyName, function(value) { original[propertyName] = value; }); } // Make a an object that has a copy of every property of the WebGL context // but wraps all functions. var wrapper = {}; for (var propertyName in ctx) { if (typeof ctx[propertyName] == 'function') { wrapper[propertyName] = makeErrorWrapper(ctx, propertyName); } else { makePropertyWrapper(wrapper, ctx, propertyName); } } // Override the getError function with one that returns our saved results. wrapper.getError = function() { for (var err in glErrorShadow) { if (glErrorShadow[err]) { glErrorShadow[err] = false; return err; } } return ctx.NO_ERROR; }; return wrapper; }; /** * Provides requestAnimationFrame in a cross browser way. * @param {function(RequestAnimationEvent): void} callback. Callback that will * be called when a frame is ready. * @param {!Element} element Element to request an animation frame for. * @return {number} request id. */ tdl.webgl.requestAnimationFrame = function(callback, element) { if (!tdl.webgl.requestAnimationFrameImpl_) { tdl.webgl.requestAnimationFrameImpl_ = function() { var functionNames = [ "requestAnimationFrame", "webkitRequestAnimationFrame", "mozRequestAnimationFrame", "oRequestAnimationFrame", "msRequestAnimationFrame" ]; for (var jj = 0; jj < functionNames.length; ++jj) { var functionName = functionNames[jj]; if (window[functionName]) { tdl.log("using ", functionName); return function(name) { return function(callback, element) { return window[name].call(window, callback, element); }; }(functionName); } } tdl.log("using window.setTimeout"); return function(callback, element) { return window.setTimeout(callback, 1000 / 70); }; }(); } return tdl.webgl.requestAnimationFrameImpl_(callback, element); }; /** * Provides cancelRequestAnimationFrame in a cross browser way. * @param {number} requestId. */ tdl.webgl.cancelRequestAnimationFrame = function(requestId) { if (!tdl.webgl.cancelRequestAnimationFrameImpl_) { tdl.webgl.cancelRequestAnimationFrameImpl_ = function() { var functionNames = [ "cancelRequestAnimationFrame", "webkitCancelRequestAnimationFrame", "mozCancelRequestAnimationFrame", "oCancelRequestAnimationFrame", "msCancelRequestAnimationFrame" ]; for (var jj = 0; jj < functionNames.length; ++jj) { var functionName = functionNames[jj]; if (window[functionName]) { return function(name) { return function(requestId) { window[name].call(window, requestId); }; }(functionName); } } return function(requestId) { window.clearTimeout(requestId); }; }(); } tdl.webgl.cancelRequestAnimationFrameImpl_(requestId); };